淺談 Entity Framework 的導覽屬性與外鍵的同步更新
TLDR
- 導覽屬性(Navigation Property)同步的前提是相關的 Entity 必須處於追蹤(Tracked)狀態。
- 任何觸發異動追蹤檢核的操作(如
Add、Entry、SaveChanges或Find),都會自動同步更新導覽屬性與外鍵屬性。 - 導覽屬性是否同步,不影響
SaveChanges執行後的資料庫正確性,EF Core 會在儲存前自動進行檢核。 - 使用
main.Subs.Remove(sub)僅會解除關聯,若要刪除子表資料,必須使用context.Subs.Remove(sub)。 - 若外鍵允許
null,解除關聯會將外鍵設為null而非刪除子表。
Entity 結構定義
本測試使用 Microsoft.EntityFrameworkCore 8。
csharp
public partial class Main {
public long Id { get; set; }
public virtual ICollection<Sub> Subs { get; set; } = new List<Sub>();
}
public partial class Sub {
public long Id { get; set; }
public long MainId { get; set; }
public virtual Main Main { get; set; }
}主表使用導覽屬性關聯子表
追蹤狀態對同步的影響
什麼情況下會遇到這個問題:在尚未呼叫 SaveChanges() 前,需要確認導覽屬性是否已自動關聯。
- 未追蹤:若
main與sub皆未加入追蹤,sub.Main為null。 - 僅主表追蹤:當
main加入追蹤後,會同步追蹤sub,sub.Main會自動更新。 - 僅子表追蹤:若僅追蹤
sub而不追蹤main,sub.Main不會同步更新。 - 先追蹤後設定:若先追蹤
main再執行main.Subs.Add(sub),sub.Main在SaveChanges()前為null,但執行後會自動同步。
子表使用導覽屬性關聯主表
什麼情況下會遇到這個問題:直接操作子表的導覽屬性來建立關聯時。
- 未追蹤:直接設置
sub.Main = main,若兩者皆未追蹤,main.Subs仍為空集合。 - 僅主表追蹤:當
main加入追蹤但sub未加入追蹤時,main.Subs仍為空集合。 - 僅子表追蹤:僅追蹤
sub時,EF 會自動同步追蹤main,此時main.Subs會包含sub。
使用外鍵屬性設定關聯
什麼情況下會遇到這個問題:透過直接修改 MainId 欄位來建立關聯時。
- 僅追蹤子表:僅追蹤
sub並設置MainId,導覽屬性不會同步。 - 雙方皆追蹤:在兩者皆被追蹤的情況下,導覽屬性會自動同步。
- 追蹤後修改外鍵:若在加入追蹤後才設置
MainId,導覽屬性在SaveChanges()前不會同步,但執行後會更新。 - Find() 取得資料:使用
Find()取得已追蹤的Main資料時,導覽屬性會自動同步;若取得的是未追蹤的Main,則不會同步。
其他操作與行為分析
SaveChanges 失敗與 Entry 呼叫
- SaveChanges 失敗:即使
SaveChanges()因資料庫限制(如主鍵重複)執行失敗,EF 內部的導覽屬性仍會完成同步。 - Entry 觸發:執行
context.Entry(entity)會強制觸發異動追蹤檢核,進而同步已追蹤 Entity 的導覽屬性。
刪除資料的注意事項
- 刪除子表:必須使用
context.Subs.Remove(sub)才能將資料從資料庫移除。 - 解除關聯:使用
main.Subs.Remove(sub)僅會解除關聯。- 若為多對多關聯,此操作會從聯結表中移除記錄。
- 若外鍵允許
null,此操作會將sub.MainId設為null,而不會刪除子表資料。
異動歷程
- 初版文件建立。
- 補上對應 GitHub 範例專案連結。